#include "laurin-monitor-device.h"
#include "laurin-utils.h"
#include <net/if.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/family.h>
#include <linux/nl80211.h>
#include <string.h>



/*------------ from iw tools ------------------*/

struct nl80211_state {
	struct nl_handle *nl_handle;
	struct nl_cache *nl_cache;
	struct genl_family *nl80211;
};


static int nl80211_init(struct nl80211_state *state)
{
	int err;

	state->nl_handle = nl_handle_alloc();
	if (!state->nl_handle) {
		fprintf(stderr, "Failed to allocate netlink handle.\n");
		return -ENOMEM;
	}

	if (genl_connect(state->nl_handle)) {
		fprintf(stderr, "Failed to connect to generic netlink.\n");
		err = -ENOLINK;
		goto out_handle_destroy;
	}

	state->nl_cache = genl_ctrl_alloc_cache(state->nl_handle);
	if (!state->nl_cache) {
		fprintf(stderr, "Failed to allocate generic netlink cache.\n");
		err = -ENOMEM;
		goto out_handle_destroy;
	}

	state->nl80211 = genl_ctrl_search_by_name(state->nl_cache, "nl80211");
	if (!state->nl80211) {
		fprintf(stderr, "nl80211 not found.\n");
		err = -ENOENT;
		goto out_cache_free;
	}

	return 0;

	out_cache_free:
		nl_cache_free(state->nl_cache);
	out_handle_destroy:
		nl_handle_destroy(state->nl_handle);
	return err;
}


static void nl80211_cleanup(struct nl80211_state *state)
{
	genl_family_put(state->nl80211);
	nl_cache_free(state->nl_cache);
	nl_handle_destroy(state->nl_handle);
}


static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err,
			 void *arg)
{
	int *ret = (int*)arg;
	*ret = err->error;
	return NL_STOP;
}

static int finish_handler(struct nl_msg *msg, void *arg)
{
	int *ret = (int*)arg;
	*ret = 0;
	return NL_SKIP;
}

static int ack_handler(struct nl_msg *msg, void *arg)
{
	int *ret = (int*)arg;
	*ret = 0;
	return NL_STOP;
}

/*--------------------------------------------------------------*/



LaurinMonitorDevice::LaurinMonitorDevice(const Glib::ustring& ifaceName, LaurinDevice* masterDevice): LaurinDevice(ifaceName), LaurinCaptureInterface(ifaceName)
{
	_masterDevice = masterDevice;
	_ready = masterDevice->setDeviceUp();

	// is device alredy present?
	if(getDeviceIndex() <= 0)
	{
		if(_ready)
			_ready = createMonitorDevice();
	}
	else
	{
		setDeviceUp();
		openCaptureContext();
		_ready = getDataLayerLink() == DLT_IEEE802_11_RADIO;
	}

}


LaurinMonitorDevice::~LaurinMonitorDevice()
{
	//don't delete master device
	removeMonitorDevice();
}


bool LaurinMonitorDevice::isInitialized() const
{
	return _ready && isCaptureContextOpen();
}


bool LaurinMonitorDevice::createMonitorDevice()
{

	if(!netlinkSendOperation(1))
		return false;

	if(setDeviceUp())
	{
		// reload iface index.
		_ifaceIndex = if_nametoindex(_ifaceName.c_str());

		openCaptureContext();

		if(isCaptureContextOpen() && getDataLayerLink() == DLT_IEEE802_11_RADIO)
			return true;
	}

	return false;
}


bool LaurinMonitorDevice::netlinkSendOperation(short op)
{
	struct nl80211_state nlstate;
	struct nl_msg *msg = NULL;
	struct nl_cb *cb = NULL;

	// initialize nl80211 (the 80211 netlink interface)
	int err = nl80211_init(&nlstate);

	if(err)
		return false;

	// allocate a message
	msg = nlmsg_alloc();

	if (!msg) {
		fprintf(stderr,"FAILED to allocate netlink message\n");
		nl80211_cleanup(&nlstate);
		return false;
	}

	// allocate callback
	cb = nl_cb_alloc(NL_CB_DEFAULT);

	if (!cb) {
		fprintf(stderr,"FAILED to allocate netlink callbacks\n");
		nlmsg_free(msg);
		return false;
	}

	// ADD operation?
	if(op == 1)
	{
		genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, genl_family_get_id(nlstate.nl80211), 0, 0,
	 			NL80211_CMD_NEW_INTERFACE, 0);

		nla_put_u32(msg, NL80211_ATTR_IFINDEX, _masterDevice->getDeviceIndex());
		nla_put_string(msg, NL80211_ATTR_IFNAME, _ifaceName.c_str());
		nla_put_u32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_MONITOR);

	}
	// REMOVE operation?
	else if(op == 0)
	{
		genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, genl_family_get_id(nlstate.nl80211), 0, 0,
	 			NL80211_CMD_DEL_INTERFACE, 0);

		nla_put_u32(msg, NL80211_ATTR_IFINDEX, _ifaceIndex);

	}


	err = nl_send_auto_complete(nlstate.nl_handle, msg);

	nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err);
	nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err);
	nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err);

	while (err > 0)
		nl_recvmsgs(nlstate.nl_handle, cb);

	nl80211_cleanup(&nlstate);
	nlmsg_free(msg);
	free(cb);

	return true;
}


bool LaurinMonitorDevice::setCaptureOptions()
{
	return LaurinCaptureInterface::setCaptureOptions() && (pcap_set_promisc(getCaptureContext(), 1) >= 0);
}


void LaurinMonitorDevice::removeMonitorDevice()
{
	netlinkSendOperation(0);
}


LaurinDevice* LaurinMonitorDevice::getMasterDevice() const
{
	return _masterDevice;
}


